package org.conditure.web.http;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.conditure.web.WebPayload;

import com.google.common.base.Strings;

/**
 * Represents a http payload.
 * 
 * @author Elvir
 */
public abstract class HttpPayload extends WebPayload {
	protected Map<String, String> headers;
	protected String body;

	/**
	 * Creates an empty payload.
	 */
	public HttpPayload() {
		super();

		headers = new HashMap<String, String>();
	}
	
	/**
	 * Loads the message from the stream into this payload.
	 * 
	 * @param stream
	 * @throws IOException 
	 */
	public void read(InputStream stream) throws IOException {
		if (stream == null) {
			throw new IllegalArgumentException("Input stream cannot be null");
		}
		
		InputStreamReader streamReader = null;
		BufferedReader reader = null;
		
		try {
			streamReader = new InputStreamReader(stream);
			reader = new BufferedReader(streamReader);
			
			loadHeader(reader);
			body = loadBody(reader);
		} catch (Throwable t) {
			try {
				if (reader != null) {
					reader.close();
				}
			} finally {
				if (streamReader != null) {
					streamReader.close();
				}
			}
		}
	}
	
	/**
	 * Loads the header portion of this stream into the header variables of this payload.
	 * 
	 * @param stream
	 * @throws IOException
	 */
	protected void loadHeader(BufferedReader reader) throws IOException {
		String line = null;
		int lineCount = 0;
		
		while (!Strings.isNullOrEmpty(line = reader.readLine())) {
			if (lineCount == 0) {
				setFirstHeaderLine(line);
			} else {
				String[] split = line.split(":", 2);
				
				if (split.length == 2) {
					headers.put(Strings.nullToEmpty(split[0]).trim(),
							Strings.nullToEmpty(split[1]).trim());
				}
			}
			
			lineCount++;
		}
	}

	/**
	 * Loads the body portion of this stream;
	 * 
	 * @param stream
	 */
	protected String loadBody(BufferedReader reader) throws IOException {
		// TODO: Use encoding based on Content-Type charset.
		
		StringWriter writer = new StringWriter();
		
		char[] buffer = new char[64];
		int read = 0;
		
		while ((read = reader.read(buffer)) > 0) {
			writer.write(buffer, 0, read);
		}
		
		return writer.toString();
	}
	
	/**
	 * Processes either the request or the response line.
	 * 
	 * @param line
	 */
	protected abstract void setFirstHeaderLine(String line) throws IOException;
	
	/**
	 * Gets the first header line.
	 * 
	 * @return
	 */
	protected abstract String getFirstHeaderLine();
	
	/**
	 * Writes the request to the output stream.
	 * 
	 * @param stream
	 * @throws IOException 
	 * 
	 * @see org.conditure.web.WebPayload#write(java.io.OutputStream)
	 */
	public void write(OutputStream stream) throws IOException {
		PrintWriter writer = null;
		
		try {
			writer = new PrintWriter(stream);
			
			writer.println(getFirstHeaderLine());
			
			for (Entry<String, String> header : headers.entrySet()) {
				writer.append(header.getKey()).append(": ").append(header.getValue());
				writer.println();
			}
			
			writer.println();
			
			if (!Strings.isNullOrEmpty(body)) {
				writer.print(body);
			}
			
			writer.flush();
		} finally {
			if (writer != null) {
				writer.close();
			}
		}
	}

	/**
	 * Gets the header value.
	 * 
	 * @param name
	 * @return
	 */
	public String getHeader(String name) {
		return headers.get(name);
	}
	
	/**
	 * Sets a header value.
	 * 
	 * @param name
	 * @param value
	 */
	public void setHeader(String name, String value) {
		if (Strings.isNullOrEmpty(name)) {
			throw new IllegalArgumentException("Header name cannot be null or empty");
		}
		
		if (value == null) {
			throw new IllegalArgumentException("Header value cannot be null");
		}
		
		headers.put(name, value);
	}

	/**
	 * @return the body
	 */
	public String getBody() {
		return body;
	}

	/**
	 * @param body the body to set
	 */
	public void setBody(String body) {
		this.body = body;
	}
}
